<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>简谱钢琴播放器 - 完整音频导出</title>
    <script src="../../js/vue3.js"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
    <style>
        /* 样式保持不变 */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        
        body {
            background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
            color: #fff;
            min-height: 100vh;
            padding: 20px;
            display: flex;
            justify-content: center;
            align-items: center;
        }
        
        .container {
            max-width: 1200px;
            width: 100%;
            background: rgba(25, 25, 35, 0.9);
            border-radius: 20px;
            box-shadow: 0 15px 35px rgba(0, 0, 0, 0.5);
            overflow: hidden;
        }
        
        header {
            background: linear-gradient(90deg, #2c3e50, #4a235a);
            padding: 25px 40px;
            text-align: center;
            border-bottom: 2px solid #ff6b6b;
        }
        
        h1 {
            font-size: 2.8rem;
            margin-bottom: 10px;
            color: #ffd166;
            text-shadow: 0 0 10px rgba(255, 209, 102, 0.5);
        }
        
        .subtitle {
            font-size: 1.2rem;
            opacity: 0.8;
            margin-top: 10px;
        }
        
        .app-content {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 30px;
            padding: 30px;
        }
        
        @media (max-width: 900px) {
            .app-content {
                grid-template-columns: 1fr;
            }
        }
        
        .input-section, .output-section {
            background: rgba(40, 40, 50, 0.7);
            border-radius: 15px;
            padding: 25px;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        }
        
        h2 {
            color: #06d6a0;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 2px solid #06d6a0;
        }
        
        .form-group {
            margin-bottom: 20px;
        }
        
        label {
            display: block;
            margin-bottom: 8px;
            font-weight: bold;
            color: #ffd166;
        }
        
        textarea, select, input {
            width: 100%;
            padding: 12px 15px;
            border-radius: 10px;
            border: 2px solid #4a235a;
            background: rgba(30, 30, 40, 0.8);
            color: white;
            font-size: 1rem;
            transition: border-color 0.3s;
        }
        
        textarea:focus, select:focus, input:focus {
            border-color: #ff6b6b;
            outline: none;
            box-shadow: 0 0 10px rgba(255, 107, 107, 0.5);
        }
        
        textarea {
            min-height: 250px;
            resize: vertical;
            font-family: monospace;
        }
        
        .control-panel {
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            margin-top: 30px;
        }
        
        button {
            flex: 1;
            min-width: 120px;
            padding: 14px 20px;
            border: none;
            border-radius: 10px;
            background: linear-gradient(135deg, #06d6a0, #118ab2);
            color: white;
            font-weight: bold;
            font-size: 1.1rem;
            cursor: pointer;
            transition: all 0.3s ease;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
        }
        
        button:hover {
            transform: translateY(-3px);
            box-shadow: 0 7px 15px rgba(6, 214, 160, 0.4);
        }
        
        button:active {
            transform: translateY(1px);
        }
        
        button.stop {
            background: linear-gradient(135deg, #ff6b6b, #ef476f);
        }
        
        button.stop:hover {
            box-shadow: 0 7px 15px rgba(255, 107, 107, 0.4);
        }
        
        button.export {
            background: linear-gradient(135deg, #ffd166, #ff9e00);
            color: #333;
        }
        
        button.export:hover {
            box-shadow: 0 7px 15px rgba(255, 209, 102, 0.4);
        }
        
        button:disabled {
            background: #555;
            transform: none;
            box-shadow: none;
            cursor: not-allowed;
        }
        
        .progress-container {
            margin-top: 20px;
            background: rgba(50, 50, 60, 0.7);
            border-radius: 10px;
            padding: 15px;
        }
        
        .progress-bar {
            height: 20px;
            background: #333;
            border-radius: 10px;
            overflow: hidden;
            margin-top: 10px;
            position: relative;
        }
        
        .progress-fill {
            height: 100%;
            background: linear-gradient(90deg, #06d6a0, #ffd166);
            width: 0%;
            transition: width 0.3s ease;
        }
        
        .status-text {
            margin-top: 15px;
            text-align: center;
            font-weight: bold;
            color: #ffd166;
            min-height: 24px;
        }
        
        .piano-container {
            background: #2d2d2d;
            border-radius: 10px;
            padding: 20px;
            margin-top: 20px;
            box-shadow: inset 0 0 15px rgba(0, 0, 0, 0.5);
        }
        
        .piano {
            display: flex;
            height: 150px;
            position: relative;
        }
        
        .white-key {
            flex: 1;
            background: linear-gradient(to bottom, #fff 0%, #eee 100%);
            border: 1px solid #999;
            border-radius: 0 0 5px 5px;
            box-shadow: 0 5px 5px rgba(0, 0, 0, 0.3);
            z-index: 1;
            position: relative;
            cursor: pointer;
        }
        
        .white-key.active {
            background: linear-gradient(to bottom, #ffd166, #ff9e00);
            box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.3);
        }
        
        .black-key {
            position: absolute;
            width: 60%;
            height: 60%;
            background: linear-gradient(to bottom, #333 0%, #000 100%);
            border-radius: 0 0 3px 3px;
            z-index: 2;
            box-shadow: 0 3px 3px rgba(0, 0, 0, 0.5);
            cursor: pointer;
        }
        
        .black-key.active {
            background: linear-gradient(to bottom, #ef476f, #d90429);
            box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.5);
        }
        
        .note-display {
            background: rgba(30, 30, 40, 0.8);
            border-radius: 10px;
            padding: 15px;
            margin-top: 20px;
            max-height: 300px;
            overflow-y: auto;
        }
        
        .note-item {
            padding: 10px;
            margin-bottom: 8px;
            background: rgba(50, 50, 70, 0.5);
            border-radius: 8px;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        
        .note-info {
            display: flex;
            gap: 15px;
        }
        
        .note-pitch {
            font-weight: bold;
            color: #ffd166;
            min-width: 60px;
        }
        
        .note-duration {
            color: #06d6a0;
        }
        
        .note-dynamics {
            color: #ef476f;
        }
        
        .tutorial-section {
            background: rgba(50, 50, 70, 0.5);
            border-radius: 10px;
            padding: 20px;
            margin-bottom: 20px;
        }
        
        .tutorial-section h3 {
            color: #ffd166;
            margin-bottom: 15px;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .tutorial-content {
            line-height: 1.6;
        }
        
        .tutorial-grid {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 15px;
            margin-top: 15px;
        }
        
        .tutorial-item {
            background: rgba(40, 40, 55, 0.7);
            padding: 12px;
            border-radius: 8px;
            border-left: 3px solid #06d6a0;
        }
        
        .tutorial-item h4 {
            color: #ffd166;
            margin-bottom: 8px;
            display: flex;
            align-items: center;
            gap: 8px;
        }
        
        .tutorial-example {
            font-family: monospace;
            background: rgba(0, 0, 0, 0.3);
            padding: 5px 10px;
            border-radius: 5px;
            display: inline-block;
            margin-top: 5px;
        }
        
        .audio-loading {
            display: flex;
            justify-content: center;
            align-items: center;
            gap: 10px;
            margin-top: 15px;
        }
        
        .spinner {
            width: 20px;
            height: 20px;
            border: 3px solid rgba(255, 255, 255, 0.3);
            border-radius: 50%;
            border-top-color: #ffd166;
            animation: spin 1s linear infinite;
        }
        
        @keyframes spin {
            to { transform: rotate(360deg); }
        }
        
        .audio-stats {
            background: rgba(40, 40, 50, 0.7);
            border-radius: 10px;
            padding: 15px;
            margin-top: 15px;
        }
        
        .stat-row {
            display: flex;
            justify-content: space-between;
            margin-bottom: 8px;
        }
        
        .export-status {
            margin-top: 15px;
            padding: 15px;
            background: rgba(40, 50, 60, 0.7);
            border-radius: 10px;
            text-align: center;
        }
        
        .download-link {
            display: inline-block;
            margin-top: 15px;
            padding: 12px 25px;
            background: linear-gradient(135deg, #ffd166, #ff9e00);
            color: #333;
            text-decoration: none;
            border-radius: 10px;
            font-weight: bold;
            transition: all 0.3s;
        }
        
        .download-link:hover {
            transform: translateY(-3px);
            box-shadow: 0 7px 15px rgba(255, 209, 102, 0.4);
        }
    </style>
</head>
<body>
    <div id="app" class="container">
        <header>
            <h1>简谱钢琴播放器</h1>
            <p class="subtitle">完整音频导出功能 - 支持真实WAV文件生成</p>
        </header>
        
        <div class="app-content">
            <div class="input-section">
                <h2>简谱输入</h2>
                
                <div class="tutorial-section">
                    <h3><i class="fas fa-graduation-cap"></i> 简谱输入格式说明</h3>
                    <div class="tutorial-content">
                        <p>简谱使用数字表示音高，空格分隔音符，支持力度标记和八度标记：</p>
                        
                        <div class="tutorial-grid">
                            <div class="tutorial-item">
                                <h4><i class="fas fa-music"></i> 基本音符</h4>
                                <p>数字 1-7 表示音阶 (Do Re Mi Fa Sol La Si)</p>
                                <div class="tutorial-example">1 2 3 4 5 6 7</div>
                            </div>
                            
                            <div class="tutorial-item">
                                <h4><i class="fas fa-volume-up"></i> 力度标记</h4>
                                <p>ff(很强), f(强), mf(中强), mp(中弱), p(弱), pp(很弱)</p>
                                <div class="tutorial-example">f 1 2 3 | p 4 5 6</div>
                            </div>
                            
                            <div class="tutorial-item">
                                <h4><i class="fas fa-arrows-alt-v"></i> 八度标记</h4>
                                <p>音符后加 <strong>'</strong> 表示高八度<br>加 <strong>,</strong> 表示低八度</p>
                                <div class="tutorial-example">1' (高音Do) | 1, (低音Do)</div>
                            </div>
                            
                            <div class="tutorial-item">
                                <h4><i class="fas fa-clock"></i> 时值标记</h4>
                                <p>数字后缀表示时值：<br>
                                    1=全音符(4拍), 2=二分音符(2拍)<br>
                                    4=四分音符(1拍), 8=八分音符(0.5拍)<br>
                                    16=十六分音符(0.25拍), 32=三十二分音符(0.125拍)<br>
                                    加 <strong>.</strong> 表示附点音符<br>
                                    加 <strong>-</strong> 表示延音（每个-增加0.5拍）</p>
                                <div class="tutorial-example">1 (全音符Do)<br>2 (二分音符Re)<br>4. (附点四分音符Fa)<br>1- (1.5拍)<br>1-- (2拍)<br>1--- (3拍)</div>
                            </div>
                            
                            <div class="tutorial-item">
                                <h4><i class="fas fa-pause"></i> 休止符</h4>
                                <p>使用 <strong>0</strong> 表示休止符</p>
                                <div class="tutorial-example">1 4 0 4 2 4</div>
                            </div>
                            
                            <div class="tutorial-item">
                                <h4><i class="fas fa-bars"></i> 小节分隔</h4>
                                <p>使用竖线 <strong>|</strong> 分隔小节</p>
                                <div class="tutorial-example">| 1 4 2 4 | 3 4 4 4 |</div>
                            </div>
                            
                            <div class="tutorial-item">
                                <h4><i class="fas fa-arrow-up"></i> 升半音标记</h4>
                                <p>使用 <strong>#</strong> 表示升半音</p>
                                <div class="tutorial-example">1# (升Do) | 5# (升Sol)</div>
                            </div>
                        </div>
                    </div>
                </div>
                
                <div class="form-group">
                    <label for="score-input">简谱内容 (使用空格分隔音符)</label>
                    <textarea id="score-input" v-model="scoreText" placeholder="示例：
| mf 1 1 2 2 | f 1' 4. 2 8 | p 0 4 3 4 |
| 5 4 6 4 | 5 4 3 4 | 2 2 1 1 |
| 1# 2# 4# 5# |"></textarea>
                </div>
                
                <div class="form-group">
                    <label for="time-signature">节拍 (如 4/4)</label>
                    <input id="time-signature" type="text" v-model="timeSignature" placeholder="4/4">
                </div>
                
                <div class="form-group">
                    <label for="bpm">速度 (BPM)</label>
                    <input id="bpm" type="number" v-model.number="bpm" min="40" max="240">
                </div>
                
                <div class="form-group">
                    <label for="instrument">乐器</label>
                    <select id="instrument" v-model="selectedInstrument">
                        <option v-for="(inst, index) in instruments" :key="index" :value="inst">{{ inst.name }}</option>
                    </select>
                </div>
                
                <div class="control-panel">
                    <button @click="parseScore" :disabled="isPlaying">
                        <i class="fas fa-cogs"></i> 解析简谱
                    </button>
                    <button @click="play" :disabled="isPlaying || !parsedNotes.length">
                        <i class="fas fa-play"></i> {{ isPlaying ? '播放中...' : '播放' }}
                    </button>
                    <button class="stop" @click="stop" :disabled="!isPlaying">
                        <i class="fas fa-stop"></i> 停止
                    </button>
                    <button class="export" @click="exportWAV" :disabled="isPlaying || !parsedNotes.length">
                        <i class="fas fa-download"></i> 导出音频
                    </button>
                </div>
                
                <div class="progress-container">
                    <label>播放进度</label>
                    <div class="progress-bar">
                        <div class="progress-fill" :style="{ width: progress + '%' }"></div>
                    </div>
                    <div class="status-text">
                        <span v-if="isPlaying" class="pulse"></span>
                        {{ statusMessage }}
                    </div>
                </div>
                
                <div class="audio-loading" v-if="isLoadingAudio">
                    <div class="spinner"></div>
                    <span>正在加载音频资源 ({{ loadedAudioCount }}/{{ totalAudioCount }})</span>
                </div>
                
                <div class="audio-stats">
                    <div class="stat-row">
                        <span>音频缓存:</span>
                        <span>{{ cachedAudioCount }} 个音符</span>
                    </div>
                    <div class="stat-row">
                        <span>当前音量:</span>
                        <span>{{ volume }}%</span>
                    </div>
                </div>
                
                <div class="export-status" v-if="isExporting">
                    <div class="spinner"></div>
                    <p>正在生成音频文件... {{ exportProgress }}%</p>
                </div>
                
                <div class="export-status" v-if="exportCompleted">
                    <i class="fas fa-check-circle" style="color:#06d6a0;font-size:2rem;"></i>
                    <p>音频导出成功！</p>
                    <a :href="audioUrl" :download="audioFileName" class="download-link">
                        <i class="fas fa-download"></i> 下载 WAV 文件
                    </a>
                </div>
            </div>
            
            <div class="output-section">
                <h2>播放控制与输出</h2>
                
                <div class="piano-container">
                    <label>虚拟钢琴 (点击试弹)</label>
                    <div class="piano">
                        <div class="white-key" v-for="(key, index) in pianoKeys" :key="index"
                             :class="{ active: activeNote === key.note }"
                             @mousedown="playKey(key.note)"></div>
                        <div class="black-key" v-for="(key, index) in blackKeys" :key="'b'+index"
                             :style="{ left: key.position + '%' }"
                             :class="{ active: activeNote === key.note }"
                             @mousedown="playKey(key.note)"></div>
                    </div>
                </div>
                
                <div class="note-display">
                    <label>解析结果预览</label>
                    <div v-if="parsedNotes.length">
                        <div v-for="(note, index) in parsedNotes" :key="index" class="note-item">
                            <div class="note-info">
                                <div class="note-pitch">{{ note.fullName || 'REST' }}</div>
                                <div class="note-duration">时值: {{ note.durationStr }}</div>
                                <div class="note-dynamics">力度: {{ note.dynamics.toUpperCase() }}</div>
                            </div>
                            <div v-if="currentNoteIndex === index" class="pulse"></div>
                        </div>
                    </div>
                    <div v-else class="note-item">
                        <p>没有解析结果，请在上方输入简谱并点击解析</p>
                    </div>
                </div>
            </div>
        </div>
        
        <footer>
            <p>简谱钢琴播放器 &copy; 2023 | 基于Web Audio API和Vue.js构建</p>
        </footer>
    </div>

    <script>
        const { createApp, ref, reactive, computed, onMounted, watch } = Vue;
        
        createApp({
            setup() {
                // 应用状态
                const scoreText = ref(`|1 2 3 4 5 6 7 1' | 1# 2# 4# 5# 6# |`);
                const timeSignature = ref('4/4');
                const bpm = ref(90);
                const statusMessage = ref('准备就绪');
                const isPlaying = ref(false);
                const progress = ref(0);
                const currentNoteIndex = ref(-1);
                const activeNote = ref('');
                const parsedNotes = ref([]);
                const audioContext = ref(null);
                const audioBuffers = ref({});
                const playbackStartTime = ref(0);
                const playbackId = ref(0);
                const isLoadingAudio = ref(false);
                const loadedAudioCount = ref(0);
                const totalAudioCount = ref(0);
                const cachedAudioCount = ref(0);
                const volume = ref(80);
                const isExporting = ref(false);
                const exportProgress = ref(0);
                const exportCompleted = ref(false);
                const audioUrl = ref('');
                const audioFileName = ref('');
                
                // 乐器选项
                const instruments = ref([
                    { name: '钢琴', value: 'acoustic_grand_piano-mp3' },
                    { name: '吉他', value: 'acoustic_guitar_steel-mp3' },
                    { name: '小提琴', value: 'violin-mp3' },
                    { name: '音乐盒', value: 'music_box-mp3' }
                ]);
                const selectedInstrument = ref(instruments.value[0]);
                
                // 钢琴键盘布局
                const pianoKeys = ref([
                    { note: 'C4' }, { note: 'D4' }, { note: 'E4' }, 
                    { note: 'F4' }, { note: 'G4' }, { note: 'A4' }, 
                    { note: 'B4' }, { note: 'C5' }
                ]);
                
                const blackKeys = ref([
                    { note: 'C#4', position: 9.5 },
                    { note: 'D#4', position: 24.5 },
                    { note: 'F#4', position: 52 },
                    { note: 'G#4', position: 66.5 },
                    { note: 'A#4', position: 81 }
                ]);
                
                // 初始化音频上下文
                const initAudioContext = () => {
                    if (!audioContext.value) {
                        audioContext.value = new (window.AudioContext || window.webkitAudioContext)();
                    }
                };
                
                // 获取音符的音频URL
                const getNoteUrl = (note) => {
                    // 使用指定的音源URL
                    const baseUrl = '/asset/FatBoy/';
					if(note.match("C#")){
						note=note.replace("C#","Db");
					}else if(note.match("D#")){
						note=note.replace("D#","Eb");
					}else if(note.match("E#")){
						note=note.replace("E#","Fb");
					}else if(note.match("F#")){
						note=note.replace("F#","Gb");
					}else if(note.match("G#")){
						note=note.replace("G#","Ab");
					}else if(note.match("A#")){
						note=note.replace("A#","Bb");
					}else if(note.match("B#")){
							note=note.replace("B#","Cb");
						}
					return `${baseUrl}${selectedInstrument.value.value}/${note}.mp3`;
                };
                
                // 加载单个音符的音频
                const loadNoteAudio = async (note) => {
                    if (audioBuffers.value[note]) {
                        return audioBuffers.value[note];
                    }
                    
                    const url = getNoteUrl(note);
                    
                    try {
                        const response = await fetch(url);
                        const arrayBuffer = await response.arrayBuffer();
                        const audioBuffer = await audioContext.value.decodeAudioData(arrayBuffer);
                        audioBuffers.value[note] = audioBuffer;
                        cachedAudioCount.value = Object.keys(audioBuffers.value).length;
                        return audioBuffer;
                    } catch (error) {
                        console.error(`无法加载音符 ${note} 的音频:`, error);
                        return null;
                    }
                };
                
                // 播放单个音符
                const playNote = (note, dynamics = 'mf', durationInSeconds = 0.5) => {
                    initAudioContext();
                    
                    // 计算音量（基于力度）
                    let gainValue = 1; // 默认中强
                    if (dynamics === 'f') gainValue = 1.0;
                    else if (dynamics === 'ff') gainValue = 1.2;
                    else if (dynamics === 'p') gainValue = 0.5;
                    else if (dynamics === 'pp') gainValue = 0.3;
                    
                    // 播放音符
                    if (audioBuffers.value[note]) {
                        const source = audioContext.value.createBufferSource();
                        const gainNode = audioContext.value.createGain();
                        
                        source.buffer = audioBuffers.value[note];
                        source.connect(gainNode);
                        gainNode.connect(audioContext.value.destination);
                        
                        // 设置音量
                        gainNode.gain.value = gainValue * (volume.value / 100);
                        
                        // 设置播放时间
                        const startTime = audioContext.value.currentTime;
                        source.start(startTime);
                        // 添加停止时间（解决音符重叠问题）
                        source.stop(startTime + durationInSeconds);
                        
                        activeNote.value = note;
                        
                        // 在音频播放结束后清除活动状态
                        source.onended = () => {
                            if (activeNote.value === note) {
                                activeNote.value = '';
                            }
                        };
                        
                        return source;
                    }
                    
                    // 如果音频未加载，尝试加载并播放
                    loadNoteAudio(note).then(() => {
                        playNote(note, dynamics, durationInSeconds);
                    });
                    
                    return null;
                };
                
                // 播放按键
                const playKey = (note) => {
                    playNote(note, 'mf', 0.5); // 默认播放0.5秒
                };
                
                // 解析简谱（支持所有时值类型和升半音）
                const parseScore = () => {
                    statusMessage.value = '正在解析简谱...';
                    parsedNotes.value = [];
                    
                    try {
                        // 移除小节分隔符
                        const cleanedScore = scoreText.value.replace(/\|/g, '');
                        // 分割为音符序列
                        const tokens = cleanedScore.trim().split(/\s+/);
                        
                        const baseNotes = [];
                        
                        tokens.forEach(token => {
                            if (!token) return;
                            
                            // 解析力度标记
                            let dynamics = 'mf'; // 默认中强
                            if (token.startsWith('f') || token.startsWith('p') || token.startsWith('m')) {
                                if (token.startsWith('ff')) {
                                    dynamics = 'ff';
                                    token = token.substring(2);
                                } else if (token.startsWith('f')) {
                                    dynamics = 'f';
                                    token = token.substring(1);
                                } else if (token.startsWith('pp')) {
                                    dynamics = 'pp';
                                    token = token.substring(2);
                                } else if (token.startsWith('p')) {
                                    dynamics = 'p';
                                    token = token.substring(1);
                                } else if (token.startsWith('mf')) {
                                    dynamics = 'mf';
                                    token = token.substring(2);
                                } else if (token.startsWith('mp')) {
                                    dynamics = 'mp';
                                    token = token.substring(2);
                                }
                            }
                            
                            // 计算延音线的数量
                            let dashCount = 0;
                            while (token.endsWith('-')) {
                                dashCount++;
                                token = token.substring(0, token.length - 1);
                            }
                            
                            // 处理休止符
                            if (token === '0') {
                                // 休止符的基础时值 + 延音线
                                const baseDuration = 1; // 四分休止符
                                const totalDuration = baseDuration + dashCount * 0.5;
                                
                                baseNotes.push({
                                    type: 'rest',
                                    dynamics: dynamics,
                                    durationInBeat: totalDuration,
                                    fullName: 'REST',
                                    durationStr: `休止符(${totalDuration}拍)`
                                });
                                return;
                            }
                            
                            // 解析音符、升号、八度和时值
                            const match = token.match(/^(\d)(#?)([',]*)(\d*\.?\d*)?$/);
                            if (!match) return;
                            
                            const pitchNum = match[1];
                            const isSharp = match[2] === '#';
                            const octaveMarks = match[3] || '';
                            let durationValue = match[4] || '4';
                            
                            // 计算八度偏移
                            let octaveOffset = 0;
                            for (let char of octaveMarks) {
                                if (char === ',') octaveOffset--;
                                if (char === "'") octaveOffset++;
                            }
                            const octave = 4 + octaveOffset;
                            
                            // 将数字转成音名，处理升半音
                            const noteNames = ['C', 'D', 'E', 'F', 'G', 'A', 'B'];
                            let noteName = noteNames[parseInt(pitchNum) - 1];
                            
                            // 处理升半音
                            if (isSharp) {
                                // 特殊处理：B和E没有升半音（因为B升半音=C，E升半音=F）
                                if (pitchNum === '3' || pitchNum === '7') {
                                    // 3升半音是4，7升半音是下一个八度的1
                                    if (pitchNum === '7') {
                                        noteName = 'C';
                                        octaveOffset += 1;
                                    } else {
                                        noteName = noteNames[parseInt(pitchNum)]; // 3# -> F
                                    }
                                } else {
                                    noteName += '#';
                                }
                            }
                            
                            const fullName = `${noteName}${octave + octaveOffset}`;
                            
                            // 计算基础时值 - 支持所有要求的时值类型
                            let durationInBeat = 0;
                            let durationStr = "";
                            
                            // 支持附点
                            const dotted = durationValue.includes('.');
                            
                            // 去除点符号
                            durationValue = durationValue.replace(/\.$/, '');
                            
                            // 根据输入值计算时值
                            switch(durationValue) {
                                case '1': // 全音符
                                    durationInBeat = 4;
                                    durationStr = "全音符(4拍)";
                                    break;
                                case '2': // 二分音符
                                    durationInBeat = 2;
                                    durationStr = "二分音符(2拍)";
                                    break;
                                case '4': // 四分音符
                                    durationInBeat = 1;
                                    durationStr = "四分音符(1拍)";
                                    break;
                                case '8': // 八分音符
                                    durationInBeat = 0.5;
                                    durationStr = "八分音符(0.5拍)";
                                    break;
                                case '16': // 十六分音符
                                    durationInBeat = 0.25;
                                    durationStr = "十六分音符(0.25拍)";
                                    break;
                                case '32': // 三十二分音符
                                    durationInBeat = 0.125;
                                    durationStr = "三十二分音符(0.125拍)";
                                    break;
                                default: // 默认四分音符
                                    durationInBeat = 1;
                                    durationStr = "四分音符(1拍)";
                            }
                            
                            // 处理附点音符（时值增加50%）
                            if (dotted) {
                                durationInBeat *= 1.5;
                                durationStr = `附点${durationStr.split('(')[0]}(${durationInBeat}拍)`;
                            }
                            
                            // 添加延音线的时值（每个横线增加0.5拍）
                            if (dashCount > 0) {
                                durationInBeat += dashCount * 0.5;
                                durationStr = `四分音符+${dashCount}条延音线(${durationInBeat}拍)`;
                            }
                            
                            baseNotes.push({
                                type: 'note',
                                dynamics: dynamics,
                                note: noteName,
                                octave: octave + octaveOffset,
                                fullName: fullName,
                                durationInBeat: durationInBeat,
                                durationStr: durationStr
                            });
                        });
                        
                        parsedNotes.value = baseNotes;
                        
                        statusMessage.value = `解析完成，共 ${parsedNotes.value.length} 个音符`;
                    } catch (error) {
                        statusMessage.value = `解析错误: ${error.message}`;
                        console.error(error);
                    }
                };
                
                // 预加载所需音频
                const preloadAudio = async () => {
                    initAudioContext();
                    
                    // 收集所有需要加载的音符
                    const notesToLoad = [];
                    parsedNotes.value.forEach(note => {
                        if (note.type === 'note' && !audioBuffers.value[note.fullName]) {
                            notesToLoad.push(note.fullName);
                        }
                    });
                    
                    if (notesToLoad.length === 0) {
                        return true;
                    }
                    
                    isLoadingAudio.value = true;
                    totalAudioCount.value = notesToLoad.length;
                    loadedAudioCount.value = 0;
                    
                    try {
                        for (const note of notesToLoad) {
                            await loadNoteAudio(note);
                            loadedAudioCount.value++;
                        }
                        isLoadingAudio.value = false;
                        return true;
                    } catch (error) {
                        isLoadingAudio.value = false;
                        statusMessage.value = '音频加载失败';
                        console.error('音频预加载错误:', error);
                        return false;
                    }
                };
                
                // 播放音乐
                const play = async () => {
                    if (!parsedNotes.value.length) {
                        statusMessage.value = '请先解析简谱';
                        return;
                    }
                    
                    initAudioContext();
                    
                    // 预加载音频
                    if (!isLoadingAudio.value) {
                        const preloadSuccess = await preloadAudio();
                        if (!preloadSuccess) {
                            statusMessage.value = '无法播放：音频加载失败';
                            return;
                        }
                    }
                    
                    isPlaying.value = true;
                    currentNoteIndex.value = -1;
                    progress.value = 0;
                    statusMessage.value = '开始播放...';
                    playbackStartTime.value = Date.now();
                    playbackId.value++;
                    
                    // 开始播放序列
                    playSequence();
                };
                
                // 播放音符序列
                const playSequence = () => {
                    const playbackInstance = playbackId.value;
                    let currentTime = 0;
                    
                    const playNextNote = (index) => {
                        if (playbackInstance !== playbackId.value || index >= parsedNotes.value.length) {
                            if (index >= parsedNotes.value.length) {
                                isPlaying.value = false;
                                statusMessage.value = '播放完成';
                                progress.value = 100;
                                currentNoteIndex.value = -1;
                            }
                            return;
                        }
                        
                        const note = parsedNotes.value[index];
                        currentNoteIndex.value = index;
                        
                        // 更新进度
                        progress.value = Math.round((index + 1) / parsedNotes.value.length * 100);
                        statusMessage.value = `正在播放: ${note.fullName} (${note.dynamics}, ${note.durationStr})`;
                        
                        // 播放音符（休止符不播放）
                        if (note.type === 'note') {
                            const durationInSeconds = (60 / bpm.value) * note.durationInBeat;
                            playNote(note.fullName, note.dynamics, durationInSeconds);
                        }
                        
                        // 计算延迟（毫秒）
                        const delay = (60000 / bpm.value) * note.durationInBeat;
                        
                        // 安排下一个音符
                        setTimeout(() => {
                            playNextNote(index + 1);
                        }, delay);
                    };
                    
                    playNextNote(0);
                };
                
                // 停止播放
                const stop = () => {
                    playbackId.value++;
                    isPlaying.value = false;
                    activeNote.value = '';
                    currentNoteIndex.value = -1;
                    statusMessage.value = '播放已停止';
                };
                
                // 将AudioBuffer转换为WAV
                const bufferToWav = (buffer) => {
                    const numChannels = buffer.numberOfChannels;
                    const sampleRate = buffer.sampleRate;
                    const format = 1; // PCM
                    const bitDepth = 16;
                    
                    const bytesPerSample = bitDepth / 8;
                    const blockAlign = numChannels * bytesPerSample;
                    
                    const wavData = new Uint8Array(44 + buffer.length * numChannels * 2);
                    const view = new DataView(wavData.buffer);
                    
                    // RIFF标识符
                    writeString(view, 0, 'RIFF');
                    // RIFF长度
                    view.setUint32(4, 36 + buffer.length * numChannels * 2, true);
                    // WAVE标识符
                    writeString(view, 8, 'WAVE');
                    // fmt块标识符
                    writeString(view, 12, 'fmt ');
                    // fmt块长度
                    view.setUint32(16, 16, true);
                    // 音频格式（PCM）
                    view.setUint16(20, format, true);
                    // 声道数
                    view.setUint16(22, numChannels, true);
                    // 采样率
                    view.setUint32(24, sampleRate, true);
                    // 字节率
                    view.setUint32(28, sampleRate * blockAlign, true);
                    // 块对齐
                    view.setUint16(32, blockAlign, true);
                    // 位深度
                    view.setUint16(34, bitDepth, true);
                    // data块标识符
                    writeString(view, 36, 'data');
                    // data块长度
                    view.setUint32(40, buffer.length * numChannels * 2, true);
                    
                    // 写入音频数据
                    let offset = 44;
                    for (let i = 0; i < buffer.length; i++) {
                        for (let channel = 0; channel < numChannels; channel++) {
                            const sample = Math.max(-1, Math.min(1, buffer.getChannelData(channel)[i]));
                            const int16 = sample < 0 ? sample * 0x8000 : sample * 0x7FFF;
                            view.setInt16(offset, int16, true);
                            offset += 2;
                        }
                    }
                    
                    return wavData;
                };
                
                // 辅助函数：写入字符串
                const writeString = (view, offset, string) => {
                    for (let i = 0; i < string.length; i++) {
                        view.setUint8(offset + i, string.charCodeAt(i));
                    }
                };
                
                // 导出WAV
                const exportWAV = async () => {
                    if (!parsedNotes.value.length) {
                        statusMessage.value = '请先解析简谱';
                        return;
                    }
                    
                    isExporting.value = true;
                    exportCompleted.value = false;
                    exportProgress.value = 0;
                    statusMessage.value = '开始音频导出...';
                    
                    // 确保所有音频已加载
                    const preloadSuccess = await preloadAudio();
                    if (!preloadSuccess) {
                        isExporting.value = false;
                        statusMessage.value = '无法导出：音频加载失败';
                        return;
                    }
                    
                    try {
                        // 计算总时长（秒）
                        let totalBeats = 0;
                        parsedNotes.value.forEach(note => {
                            totalBeats += note.durationInBeat;
                        });
                        const totalSeconds = (totalBeats * 60) / bpm.value;
                        
                        // 创建离线音频上下文
                        const offlineCtx = new OfflineAudioContext(
                            2, // 声道数
                            totalSeconds * 44100, // 采样数
                            44100 // 采样率
                        );
                        
                        // 调度所有音符
                        let currentTime = 0;
                        const gainNodes = [];
                        
                        for (let i = 0; i < parsedNotes.value.length; i++) {
                            const note = parsedNotes.value[i];
                            const duration = (note.durationInBeat * 60) / bpm.value;
                            
                            if (note.type === 'note') {
                                const buffer = audioBuffers.value[note.fullName];
                                if (buffer) {
                                    const source = offlineCtx.createBufferSource();
                                    const gainNode = offlineCtx.createGain();
                                    
                                    // 计算音量（基于力度）
                                    let gainValue = 0.7; // 默认中强
                                    if (note.dynamics === 'f') gainValue = 1.0;
                                    else if (note.dynamics === 'ff') gainValue = 1.2;
                                    else if (note.dynamics === 'p') gainValue = 0.5;
                                    else if (note.dynamics === 'pp') gainValue = 0.3;
                                    
                                    source.buffer = buffer;
                                    source.connect(gainNode);
                                    gainNode.connect(offlineCtx.destination);
                                    gainNode.gain.value = gainValue * (volume.value / 100);
                                    
                                    source.start(currentTime);
                                    source.stop(currentTime+duration)
                                    // 保存节点引用
                                    gainNodes.push(gainNode);
                                }
                            }
                            
                            currentTime += duration;
                            exportProgress.value = Math.round((i + 1) / parsedNotes.value.length * 100);
                        }
                        
                        // 开始渲染
                        const renderedBuffer = await offlineCtx.startRendering();
                        
                        // 转换为WAV
                        const wavData = bufferToWav(renderedBuffer);
                        const blob = new Blob([wavData], { type: 'audio/wav' });
                        
                        // 创建下载链接
                        audioUrl.value = URL.createObjectURL(blob);
                        const now = new Date();
                        audioFileName.value = `composition_${now.getFullYear()}${(now.getMonth()+1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}_${now.getHours()}${now.getMinutes()}${now.getSeconds()}.wav`;
                        
                        // 更新状态
                        isExporting.value = false;
                        exportCompleted.value = true;
                        statusMessage.value = '音频导出成功！';
                        
                    } catch (error) {
                        console.error('音频导出错误:', error);
                        isExporting.value = false;
                        statusMessage.value = '音频导出失败: ' + error.message;
                    }
                };
                
                // 初始化
                onMounted(() => {
                    parseScore();
                    
                    // 预加载常用音符
                    const commonNotes = ['C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4', 'B4', 'C5'];
                    commonNotes.forEach(note => {
                        loadNoteAudio(note);
                    });
                    
                    // 监听乐器切换
                    watch(selectedInstrument, (newInstrument) => {
                        statusMessage.value = `已切换到乐器: ${newInstrument.name}`;
                        // 清空缓存
                        audioBuffers.value = {};
                        cachedAudioCount.value = 0;
                        
                        // 重新预加载当前乐器的常用音符
                        const commonNotes = ['C4', 'C#4', 'D4', 'D#4', 'E4', 'F4', 'F#4', 'G4', 'G#4', 'A4', 'A#4', 'B4', 'C5'];
                        commonNotes.forEach(note => {
                            loadNoteAudio(note);
                        });
                    });
                });
                
                return {
                    scoreText,
                    timeSignature,
                    bpm,
                    statusMessage,
                    isPlaying,
                    progress,
                    currentNoteIndex,
                    activeNote,
                    parsedNotes,
                    instruments,
                    selectedInstrument,
                    pianoKeys,
                    blackKeys,
                    isLoadingAudio,
                    loadedAudioCount,
                    cachedAudioCount,
                    volume,
                    isExporting,
                    exportProgress,
                    exportCompleted,
                    audioUrl,
                    audioFileName,
                    parseScore,
                    playKey,
                    play,
                    stop,
                    exportWAV
                };
            }
        }).mount('#app');
    </script>
</body>
</html>